#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import time
import logging
import subprocess
import threading
from typing import Optional, Dict, Any, List, Callable

import pygame

class JoystickController:
    """
    使用pygame监听游戏手柄输入，控制进程的启动与停止
    
    :param config: 配置字典，包含按键映射和命令配置
    """
    
    def __init__(self, config: Dict[str, Any]) -> None:
        """
        初始化游戏手柄控制器
        
        :param config: 配置字典，包含按键映射和命令配置
        """
        self.config = config
        self.processes: Dict[str, subprocess.Popen] = {}
        self.running = False
        self.joystick = None
        
        # 获取日志记录器而不是创建新的
        self.logger = logging.getLogger("JoystickController")
        
        # 初始化pygame
        pygame.init()
        pygame.joystick.init()
        
        # 设置按键回调函数
        self.button_callbacks: Dict[int, Callable] = {}
        self._setup_button_callbacks()
        
        # 按键消抖相关变量
        self.debounce_time = self.config.get("debounce_time", 0.5)  # 默认消抖时间为0.5秒
        self.last_button_press: Dict[int, float] = {}  # 记录每个按钮最后一次按下的时间
    
    def _setup_logger(self) -> logging.Logger:
        """
        设置日志记录器
        
        :return: 配置好的Logger对象
        """
        logger = logging.getLogger("JoystickController")
        
        # 避免重复添加处理器
        if not logger.handlers:
            logger.setLevel(logging.INFO)
            
            handler = logging.StreamHandler()
            formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
            handler.setFormatter(formatter)
            
            logger.addHandler(handler)
        
        return logger
    
    def _setup_button_callbacks(self) -> None:
        """
        设置按钮回调函数映射
        """
        button_map = self.config.get("button_map", {})
        
        for button_id, action in button_map.items():
            if action == "start":
                self.button_callbacks[int(button_id)] = self.start_process
            elif action == "stop":
                self.button_callbacks[int(button_id)] = self.stop_process
            elif action == "restart":
                self.button_callbacks[int(button_id)] = self.restart_process
    
    def connect_joystick(self) -> bool:
        """
        连接到游戏手柄设备
        
        :return: 连接是否成功
        """
        joystick_count = pygame.joystick.get_count()
        
        if joystick_count == 0:
            self.logger.error("未检测到游戏手柄设备")
            return False
        
        self.joystick = pygame.joystick.Joystick(0)
        self.joystick.init()
        
        self.logger.info(f"已连接到游戏手柄: {self.joystick.get_name()}")
        self.logger.info(f"按钮数量: {self.joystick.get_numbuttons()}")
        self.logger.info(f"轴数量: {self.joystick.get_numaxes()}")
        
        return True
    
    def start_process(self, process_name: str = "default") -> None:
        """
        启动指定的进程
        
        :param process_name: 进程名称，默认为"default"
        """
        if process_name in self.processes and self.processes[process_name].poll() is None:
            self.logger.warning(f"进程 '{process_name}' 已经在运行中")
            return
        
        command = self.config.get("commands", {}).get(process_name)
        if not command:
            self.logger.error(f"未找到进程 '{process_name}' 的启动命令")
            return
        
        try:
            self.logger.info(f"启动进程: {command}")
            # 使用shell=True以支持复杂的命令（如管道、重定向等）
            process = subprocess.Popen(
                command, 
                shell=True, 
                stdout=subprocess.PIPE, 
                stderr=subprocess.PIPE,
                text=True
            )
            self.processes[process_name] = process
            
            # 启动线程来处理进程输出
            threading.Thread(
                target=self._monitor_process_output,
                args=(process, process_name),
                daemon=True
            ).start()
            
            self.logger.info(f"进程 '{process_name}' 已启动")
        except Exception as e:
            self.logger.error(f"启动进程 '{process_name}' 失败: {str(e)}")
    
    def _monitor_process_output(self, process: subprocess.Popen, process_name: str) -> None:
        """
        监控进程输出的线程函数
        
        :param process: 要监控的子进程
        :param process_name: 进程名称
        """
        for line in process.stdout:
            self.logger.info(f"[{process_name}] {line.strip()}")
        
        # 进程结束时记录返回码
        return_code = process.wait()
        self.logger.info(f"进程 '{process_name}' 已结束，返回码: {return_code}")
    
    def stop_process(self, process_name: str = "default") -> None:
        """
        停止指定的进程
        
        :param process_name: 进程名称，默认为"default"
        """
        if process_name not in self.processes or self.processes[process_name].poll() is not None:
            self.logger.warning(f"进程 '{process_name}' 未运行")
            return
        
        try:
            self.logger.info(f"停止进程 '{process_name}'")
            self.processes[process_name].terminate()
            
            # 给进程一些时间来优雅地终止
            time.sleep(1)
            
            # 如果进程还在运行，则强制结束
            if self.processes[process_name].poll() is None:
                self.logger.warning(f"进程 '{process_name}' 未响应终止信号，强制结束")
                self.processes[process_name].kill()
            
            self.logger.info(f"进程 '{process_name}' 已停止")
        except Exception as e:
            self.logger.error(f"停止进程 '{process_name}' 失败: {str(e)}")
    
    def restart_process(self, process_name: str = "default") -> None:
        """
        重启指定的进程
        
        :param process_name: 进程名称，默认为"default"
        """
        self.stop_process(process_name)
        self.start_process(process_name)
    
    def _should_process_button(self, button_id: int) -> bool:
        """
        检查是否应该处理按钮事件（实现按键消抖）
        
        :param button_id: 按钮ID
        :return: 如果应该处理按钮事件则返回True，否则返回False
        """
        current_time = time.time()
        
        # 如果按钮是第一次按下，或者距离上次按下已经超过消抖时间
        if button_id not in self.last_button_press or \
           current_time - self.last_button_press[button_id] > self.debounce_time:
            self.last_button_press[button_id] = current_time
            return True
        
        # 按钮在消抖时间内重复按下，忽略此次按下
        self.logger.debug(f"按钮 {button_id} 在消抖期间内重复按下，忽略")
        return False
    
    def run(self) -> None:
        """
        运行主循环，监听游戏手柄事件
        """
        if not self.connect_joystick():
            self.logger.error("无法连接到游戏手柄，退出")
            return
        
        self.running = True
        self.logger.info("开始监听游戏手柄事件")
        self.logger.info(f"按键消抖时间设置为 {self.debounce_time} 秒")
        
        try:
            while self.running:
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        self.running = False
                    elif event.type == pygame.JOYBUTTONDOWN:
                        button_id = event.button
                        self.logger.debug(f"按下按钮: {button_id}")
                        
                        # 按键消抖处理
                        if button_id in self.button_callbacks and self._should_process_button(button_id):
                            self.logger.info(f"处理按钮 {button_id} 的按下事件")
                            process_name = self.config.get("button_to_process", {}).get(str(button_id), "default")
                            self.button_callbacks[button_id](process_name)
                
                # 适当的休眠以减少CPU使用
                time.sleep(0.05)
        except KeyboardInterrupt:
            self.logger.info("接收到中断信号，退出")
        except Exception as e:
            self.logger.error(f"发生错误: {str(e)}")
        finally:
            self.cleanup()
    
    def cleanup(self) -> None:
        """
        清理资源并停止所有进程
        """
        self.logger.info("清理资源...")
        
        # 停止所有进程
        for process_name in list(self.processes.keys()):
            self.stop_process(process_name)
        
        # 清理pygame资源
        if self.joystick:
            self.joystick.quit()
        pygame.joystick.quit()
        pygame.quit()
        
        self.logger.info("已退出") 